#include <bits/alltypes.h>
#include <linux/bpf.h>
#include <string>
#include <cinttypes>
#include <memory>
#include "MultiSurface_2.h"
#include "MultiSurface_2Native.h"
#include "plugin_manager.h"
#include "hilog/log.h"
#include "av_codec_sample_error.h"

#undef LOG_DOMAIN
#undef LOG_TAG
#define LOG_DOMAIN 0x3200
#define LOG_TAG "MultiSurface_2"

struct AsyncCallbackInfo {
    napi_env env;
    napi_async_work asyncWork;
    napi_deferred deferred;

    int32_t inputFd = 0;
    int64_t inputOffset = 0;
    int64_t inputSize = 0;
    bool apiFlag = false;

    int32_t resultCode = 0;
    std::string resultStr = "";
    int64_t durationTime = 0;
};

void DealCallBack(napi_env env, void *data) {
    AsyncCallbackInfo *asyncCallbackInfo = static_cast<AsyncCallbackInfo *>(data);

    napi_value durationTime;
    napi_create_int64(env, asyncCallbackInfo->durationTime, &durationTime);
    napi_value code;
    napi_create_int32(env, asyncCallbackInfo->resultCode, &code);
    napi_value message;
    napi_create_string_utf8(env, asyncCallbackInfo->resultStr.data(), NAPI_AUTO_LENGTH, &message);
    napi_value obj;
    napi_create_object(env, &obj);

    napi_set_named_property(env, obj, "code", code);
    napi_set_named_property(env, obj, "message", message);
    napi_set_named_property(env, obj, "durationTime", durationTime);
    napi_resolve_deferred(asyncCallbackInfo->env, asyncCallbackInfo->deferred, obj);
    napi_delete_async_work(env, asyncCallbackInfo->asyncWork);
    delete asyncCallbackInfo;
}

void SetCallBackResult(AsyncCallbackInfo *asyncCallbackInfo, int32_t code, std::string str) {
    OH_LOG_INFO(LOG_APP, "%{public}s", str.c_str());
    asyncCallbackInfo->resultCode = code;
    asyncCallbackInfo->resultStr = str;
}

void SetDurationTimeCallBack(AsyncCallbackInfo *asyncCallbackInfo, int64_t durationTime) {
    OH_LOG_INFO(LOG_APP, "sample_%{public}d duraiton time in setDurationTimeCallBack is %{public}ld", durationTime);
    asyncCallbackInfo->durationTime = durationTime;
}

void NativeInitVideo(napi_env env, void *data) {
    AsyncCallbackInfo *asyncCallbackInfo = static_cast<AsyncCallbackInfo *>(data);
    SampleInfo sampleInfo;
    sampleInfo.inputFd = asyncCallbackInfo->inputFd;
    sampleInfo.inputFileOffset = asyncCallbackInfo->inputOffset;
    sampleInfo.inputFileSize = asyncCallbackInfo->inputSize;
    if (asyncCallbackInfo->apiFlag) {
        sampleInfo.codecRunMode = SURFACE_AVBUFFER;
    } else {
        sampleInfo.codecRunMode = SURFACE_ORIGIN;
    }
    sampleInfo.window = NativeXComponentSample::PluginManager::GetInstance()->multiWindowsMap["Surface2_1"];
    if (MultiSurface_2::GetInstance().Create(sampleInfo) == AVCODEC_SAMPLE_ERR_OK) {
        SetDurationTimeCallBack(asyncCallbackInfo, sampleInfo.durationTime);
        MultiSurface_2::GetInstance().isVertical
            ? SetCallBackResult(asyncCallbackInfo, 0, "native init vertical Video success")
            : SetCallBackResult(asyncCallbackInfo, 1, "native init horizontal Video success");
    } else {
        SetCallBackResult(asyncCallbackInfo, -1, "Napi g_playerSample create failed");
        int32_t ret = MultiSurface_2::GetInstance().Stop();
        if (ret != AVCODEC_SAMPLE_ERR_OK) {
            SetCallBackResult(asyncCallbackInfo, -1, "Napi g_playerSample stop failed");
        }
    }
}

void NativePause() {
    int32_t ret = MultiSurface_2::GetInstance().Pause();
    if (ret != AVCODEC_SAMPLE_ERR_OK) {
        OH_LOG_ERROR(LOG_APP, "sample 3 play again error");
    }
}

void NativePlayAgain() {
    int32_t ret = MultiSurface_2::GetInstance().Play();
    if (ret != AVCODEC_SAMPLE_ERR_OK) {
        OH_LOG_ERROR(LOG_APP, "play again error");
    }
}

void NativeSetSurface(bool isAutoSwitch) { MultiSurface_2::GetInstance().autoSwitchSurface = isAutoSwitch; }

void NativeLoop(int32_t isLoop) {
    int32_t ret = MultiSurface_2::GetInstance().Loop(isLoop);
    if (ret != AVCODEC_SAMPLE_ERR_OK) {
        OH_LOG_ERROR(LOG_APP, "loop error");
    }
}

void NativePlayVideo(napi_env env, void *data) {
    AsyncCallbackInfo *asyncCallbackInfo = static_cast<AsyncCallbackInfo *>(data);
    MultiSurface_2::GetInstance().Start();
    int32_t ret = MultiSurface_2::GetInstance().WaitForDone();
    if (ret != AVCODEC_SAMPLE_ERR_OK) {
        SetCallBackResult(asyncCallbackInfo, -1, "Napi multiSurface start failed");
        ret = MultiSurface_2::GetInstance().Stop();
        if (ret != AVCODEC_SAMPLE_ERR_OK) {
            SetCallBackResult(asyncCallbackInfo, -1, "Napi multiSurface stop failed");
        }
    }
    SetCallBackResult(asyncCallbackInfo, 0, "native playVideo success");
}

napi_value MultiSurfaceNative::InitVideo(napi_env env, napi_callback_info info) {
    int32_t inputFd = true;
    int64_t inputOffset = 0;
    int64_t inputSize = 0;
    bool apiFlag = false;

    size_t argc = 4;
    napi_value args[4] = {nullptr}; // 5: arg count
    napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);
    napi_get_value_int32(env, args[0], &inputFd);
    napi_get_value_int64(env, args[1], &inputOffset);
    napi_get_value_int64(env, args[2], &inputSize); // 2: arg inputSize offset
    napi_get_value_bool(env, args[3], &apiFlag);
    napi_value promise;
    napi_deferred deferred;
    napi_create_promise(env, &deferred, &promise);

    AsyncCallbackInfo *asyncCallbackInfo = new AsyncCallbackInfo();

    asyncCallbackInfo->env = env;
    asyncCallbackInfo->asyncWork = nullptr;
    asyncCallbackInfo->deferred = deferred;
    asyncCallbackInfo->inputFd = inputFd;
    asyncCallbackInfo->inputOffset = inputOffset;
    asyncCallbackInfo->inputSize = inputSize;
    asyncCallbackInfo->apiFlag = apiFlag;

    napi_value resourceName;
    napi_create_string_latin1(env, "videoTest", NAPI_AUTO_LENGTH, &resourceName);
    napi_create_async_work(
        env, nullptr, resourceName, [](napi_env env, void *data) { NativeInitVideo(env, data); },
        [](napi_env env, napi_status status, void *data) { DealCallBack(env, data); }, (void *)asyncCallbackInfo,
        &asyncCallbackInfo->asyncWork);
    napi_queue_async_work(env, asyncCallbackInfo->asyncWork);
    return promise;
}

napi_value MultiSurfaceNative::PlayVideo(napi_env env, napi_callback_info info) {
    napi_deferred deferred;
    napi_value promise;
    napi_create_promise(env, &deferred, &promise);

    AsyncCallbackInfo *asyncCallbackInfo = new AsyncCallbackInfo();
    asyncCallbackInfo->env = env;
    asyncCallbackInfo->asyncWork = nullptr;
    asyncCallbackInfo->deferred = deferred;

    napi_value resourceName;
    napi_create_string_latin1(env, "videoTest", NAPI_AUTO_LENGTH, &resourceName);
    napi_create_async_work(
        env, nullptr, resourceName, [](napi_env env, void *data) { NativePlayVideo(env, data); },
        [](napi_env env, napi_status status, void *data) { DealCallBack(env, data); }, (void *)asyncCallbackInfo,
        &asyncCallbackInfo->asyncWork);
    napi_queue_async_work(env, asyncCallbackInfo->asyncWork);
    return promise;
}

napi_value MultiSurfaceNative::PauseVideo(napi_env env, napi_callback_info info) {
    napi_deferred deferred;
    napi_value promise;
    napi_create_promise(env, &deferred, &promise);

    NativePause();
    return nullptr;
}

napi_value MultiSurfaceNative::PlayAgain(napi_env env, napi_callback_info info) {
    NativePlayAgain();
    return nullptr;
}

napi_value MultiSurfaceNative::VideoSeek(napi_env env, napi_callback_info info) {
    //    int64_t currentTime = 0;
    //    size_t argc = 1;
    //    napi_value args[1] = {0};
    //    napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);
    //    napi_get_value_int64(env, args[0], &currentTime);
    //
    //    int32_t ret = MultiSurface::GetInstance().VideoSeek(currentTime);
    //    if (ret != AVCODEC_SAMPLE_ERR_OK) {
    //        OH_LOG_ERROR(LOG_APP, "PlayerSample video seek failed");
    //    }
    return nullptr;
}

napi_value MultiSurfaceNative::StopVideo(napi_env env, napi_callback_info info) {
    //    int32_t ret = MultiSurface::GetInstance().Stop();
    //    if (ret != AVCODEC_SAMPLE_ERR_OK) {
    //        OH_LOG_ERROR(LOG_APP, "PlayerSample stop failed");
    //    }
    return nullptr;
}

napi_value MultiSurfaceNative::Loop(napi_env env, napi_callback_info info) {
    bool isLoop = false;

    size_t argc = 1;
    napi_value args[1] = {nullptr};
    napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);
    napi_get_value_bool(env, args[0], &isLoop);
    NativeLoop(isLoop);
    return nullptr;
}

napi_value MultiSurfaceNative::SetSurface(napi_env env, napi_callback_info info) {
    bool isAutoSwitch = false;

    size_t argc = 1;
    napi_value args[1] = {nullptr};
    napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);
    napi_get_value_bool(env, args[0], &isAutoSwitch);
    NativeSetSurface(isAutoSwitch);
    return nullptr;
}

EXTERN_C_START
static napi_value Init(napi_env env, napi_value exports) {
    napi_property_descriptor classProp[] = {
        {"initVideoNative", nullptr, MultiSurfaceNative::InitVideo, nullptr, nullptr, nullptr, napi_default, nullptr},
        {"playVideoNative", nullptr, MultiSurfaceNative::PlayVideo, nullptr, nullptr, nullptr, napi_default, nullptr},
        {"pauseVideoNative", nullptr, MultiSurfaceNative::PauseVideo, nullptr, nullptr, nullptr, napi_default, nullptr},
        {"playAgainNative", nullptr, MultiSurfaceNative::PlayAgain, nullptr, nullptr, nullptr, napi_default, nullptr},
        {"videoSeekNative", nullptr, MultiSurfaceNative::VideoSeek, nullptr, nullptr, nullptr, napi_default, nullptr},
        {"stopVideoNative", nullptr, MultiSurfaceNative::StopVideo, nullptr, nullptr, nullptr, napi_default, nullptr},
        {"loopNative", nullptr, MultiSurfaceNative::Loop, nullptr, nullptr, nullptr, napi_default, nullptr},
        {"setSurfaceNative", nullptr, MultiSurfaceNative::SetSurface, nullptr, nullptr, nullptr, napi_default, nullptr},
    };

    napi_value playerSampleNative = nullptr;
    const char *classBindName = "multiSurfaceNative_2";
    napi_define_class(env, classBindName, strlen(classBindName), nullptr, nullptr, 1, classProp, &playerSampleNative);

    OH_LOG_INFO(LOG_APP, "napi init");
    NativeXComponentSample::PluginManager::GetInstance()->Export(env, exports);
    napi_define_properties(env, exports, sizeof(classProp) / sizeof(classProp[0]), classProp);
    return exports;
}
EXTERN_C_END

static napi_module MultiSurfaceModule = {
    .nm_version = 1,
    .nm_flags = 0,
    .nm_filename = nullptr,
    .nm_register_func = Init,
    .nm_modname = "MultiSurface_2",
    .nm_priv = ((void *)0),
    .reserved = {0},
};

extern "C" __attribute__((constructor)) void RegisterEntryModule(void) { napi_module_register(&MultiSurfaceModule); }
